﻿using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;

/**
* 
* Copyright (c) 2012 Anders Høst Kjærgaard | ahkj@itu.dk
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, 
* including without limitation the rights to use, copy, modify, merge, publish, distribute, 
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial 
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

namespace Flatland.GeneticProgramming.CoreTypes {
    /// <summary>
    /// Terminal is a member of them terminal set. They are composed with a TerminalType and appropiate features are aggregated.
    /// </summary>
    [Serializable]
    public class Terminal : ICloneable {
        public String VarName { get; set; }
        public FieldInfo FieldInfo { get; set; }
        public int Index { get; set; }
        public Object Constant { get; set; }
        
        /// <summary>
        /// Get or set the methodinfo
        /// </summary>
        public MethodInfo MethodInfo { get; set; }

        /// <summary>
        /// Get or set the setype
        /// </summary>
        public SetType SetType { get; set; }

        /// <summary>
        /// Construct this terminal with MethodInfo of the target method or field. 
        /// <param name="methodInfo">Construct a terminal function.</param>
        /// </summary>
        public Terminal(MethodInfo methodInfo) {
            MethodInfo = methodInfo;
            SetType = SetType.ZERO_ARITY_FUNCTION;
        }

        /// <summary>
        /// Construct this terminal with a constant int. 
        /// <param name="fieldInfo">FieldInfo for the constant.</param>
        /// <param name="constant">Constant value.</param>
        /// </summary>
        public Terminal(FieldInfo fieldInfo, int index) {
            FieldInfo = fieldInfo;
            VarName = fieldInfo.Name;
            Index = index;
            SetType = SetType.ARRAY_INDEX;
        }

        /// <summary>
        /// Construct this terminal with a variable x. 
        /// <param name="fieldInfo">FieldInfo for the variable.</param>
        /// <param name="varName">Name of variable.</param>
        /// <param name="value">Intializer.</param>
        /// </summary>
        public Terminal(FieldInfo fieldInfo, String varName) {
            FieldInfo = fieldInfo;
            VarName = varName;
            SetType = SetType.VARIABLE;
        }

        public Terminal(Object constant) {
            Constant = constant;
            SetType = SetType.CONSTANT;
        }

        public object Clone() {
            Terminal t = null;
            switch (SetType) {
                case SetType.ZERO_ARITY_FUNCTION:
                    t = new Terminal(MethodInfo);
                    break;
                case SetType.VARIABLE:
                    t = new Terminal(FieldInfo, VarName);
                    break;
                case SetType.ARRAY_INDEX:
                    t = new Terminal(FieldInfo, Index);
                    break;
            }
            Debug.Assert(t != null, "Cloning terminal did not resolve. Terminal was null");
            return t;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj) {
            if (obj == null) return false;
            if (obj.GetType() == typeof(Terminal)) {
                return ReturnType(this) == ReturnType((Terminal)obj);
            }
            if (obj.GetType() == typeof(Function)) 
                return obj.Equals(this);
            return false;
        }

        /// <summary>
        /// True if this terminal of the command set
        /// </summary>
        /// <returns></returns>
        public bool IsCommandSet(){
            return ReturnType(this) == typeof (void);
        }

        /// <summary>
        /// Returns the relevant return type for a terminal.
        /// </summary>
        /// <param name="terminal"></param>
        /// <returns></returns>
        public static Type ReturnType(Terminal terminal) {
            switch (terminal.SetType) {
                case SetType.ZERO_ARITY_FUNCTION: {
                        return terminal.MethodInfo.ReturnType;
                    }
                case SetType.ARRAY_INDEX: {
                        return terminal.FieldInfo.FieldType.GetElementType();
                    }
                case SetType.VARIABLE: {
                        return terminal.FieldInfo.FieldType;
                    }
                case SetType.CONSTANT: {
                        return terminal.Constant.GetType();
                    }
            }

            /*
             * Unreachable code. Debug added for future.
             */
            Debug.Assert(false, "Terminal set type not resolved. Was this called with a Function?");
            return typeof(void);
        }

        /// <summary>
        /// Deserialize constructor
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public Terminal(SerializationInfo info, StreamingContext context) {
            MethodInfo = (MethodInfo)info.GetValue("MethodInfo", typeof(MethodInfo));
            FieldInfo = (FieldInfo)info.GetValue("FiledInfo", typeof(FieldInfo));
            Constant = (object)info.GetValue("Constant", typeof(Object));
            Index = (int)info.GetValue("Index", typeof(int));
        }

        /// <summary>
        /// Serialize.
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public void GetObjectData(SerializationInfo info, StreamingContext context) {
            info.AddValue("MethodInfo", MethodInfo);
            info.AddValue("FieldInfo", FieldInfo);
            info.AddValue("Constant", Constant);
            info.AddValue("Index", Index);
        }
    }
}
